# Receiver . py
"""
    Manages a thread to receive data
"""

import threading
#import datetime -- DO NOT, use Phhysical.timestamp() instead

import app.Physical as Physical
from app.Physical import tstamp as now_t

import app.RaceTrack as RaceTrack

import app.PassData as PassData
from app.PassData import PacketEvent
from app.PassData import PacketEventList




## ##########################################################################
## ##########################################################################
## ##########################################################################

# error and warning handling .... not quite sure how yet
# should use pythons built in stuff ... too lazy ... fuck off ...
my_name = None
def set_my_name(func) :
    """ use this as a decorator"""
    global my_name
    my_name = func.__name__
    return func
    my_name = None

def perror(msg) : print("ERROR: {} {}".format(my_name,msg))
def pwarn(msg)  : print("WARNING: {} {}".format(my_name,msg))


## ##########################################################################
## ##########################################################################
## ##########################################################################



## #########################################################################
## #########################################################################
## #########################################################################

## lexers -- state machines for pakets
##


@set_my_name
def lex_Arduino2014(res) :
    """ revamped
        now we are a generator that maintains its own state

        res must be a list with at least two items
        res[0] is either None or a PacketEvent to be lexed
        res[1] is the output of our func -- (s,c,nc)
        the return value from the generater can be ignored

        terminates only on error
    """

    station_p = 0 # previous station
    code_p    = 0 # previous received code
    clock_p   = 0 # previous received clock -- for wrap around
    clock_a   = 0 # clock_adjust er

    ## packet is to be
    ##    0123456789 10
    ##    sctttttttt

    while True :

      # we rely on outer res for our arg -- strange way to do it ...

        if res[0] is None :
            yield None
            continue

      # try to take the packet apart

        packet=res[0].packet.strip() # cut the crap
        if len(packet) != 10 :
            perror("got ill sized packet '{}'".format(packet) )
            return

        try:   station_n = int( packet[0] )
        except ValueError:
            perror("an inappropriate station byte in packet '{}' caused a ValueError".format(packet) )
            return

        try:   code_n = int( packet[1] )
        except ValueError:
            perror("an inappropriate code byte in packet '{}' caused a ValueError".format(packet) )
            return

        try:   clock_n = int( packet[2:], base=16 )
        except ValueError:
            perror("an inappropriate network clock field in packet '{}' caused a ValueError".format(packet) )
            return

      # make sure the clock is increasing

        if clock_n < clock_p :
            # did it wrap ?
            if clock_n < 0x00100000 and clock_p > 0xFFF00000 : # works out to about 2 1/4 minutes ...
                clock_a += 0x100000000
            else :
                perror("very strange time skip in packet '{}'".format(packet) )
                return


      # convert code to sensor number
      # clear stuff if at a different sensor

        if station_n != station_p :
            code_p = 0 # reset

        # now find the only bit that is set # fail if not exactly 1
        bit_on = ~code_p & code_n   # bits that just turned on
        sensor = -1
        while bit_on :
            if bit_on & 1 and bit_on > 1 :
                perror("more than one new bit was set in packet '{}'".format(packet) )
                return
            bit_on >>= 1
            sensor += 1
        if sensor<0 :
            perror("no new bits were set in packet '{}'".format(packet) )
            return

      # now we can yield results

        res[1] = ( station_n,sensor,clock_n+clock_a )
        yield  True
        res[1] = None

      # and advance state

        clock_p   = clock_n
        station_p = station_n
        code_p    |= code_n   # new bits that are on for next time









@set_my_name
def lex_Fantasy2014(res) :
    """ revamped
        now we are a generator that maintains its own state

        res must be a list with at least two items
        res[0] is either None or a PacketEvent to be lexed
        res[1] is the output of our func -- (s,c,nc)
        the return value from the generater can be ignored

        terminates only on error
    """

    clock_p   = 0 # previous received clock -- for wrap around
    clock_a   = 0 # clock_adjust er

    ## packet is to be
    ##    0123456789 10
    ##    sstttttttt
    ## where 1 bit of ss is the sensor and the rest is the station number

    while True :

      # we rely on outer res for our arg -- strange way to do it ...

        if res[0] is None :
            yield None
            continue

      # try to take the packet apart

        packet=res[0].packet.strip() # cut the crap
        if len(packet) != 10 :
            perror("got ill sized packet '{}'".format(packet) )
            return

        try:   ss = int( packet[0:2], base=16 )
        except ValueError:
            perror("an inappropriate station byte in packet '{}' caused a ValueError".format(packet) )
            return

        station = ss >> 1
        sensor = ss & 1

        try:   clock_n = int( packet[2:], base=16 )
        except ValueError:
            perror("an inappropriate network clock field in packet '{}' caused a ValueError".format(packet) )
            return

      # make sure the clock is increasing

        if clock_n < clock_p :
            # did it wrap ?
            if clock_n < 0x00100000 and clock_p > 0xFFF00000 : # works out to about 2 1/4 minutes ...
                clock_a += 0x100000000
            else :
                perror("very strange time skip in packet '{}'".format(packet) )
                return

      # now we can yield results

        res[1] = ( station,sensor,clock_n+clock_a )
        yield  True
        res[1] = None

      # and advance state

        clock_p   = clock_n


## #########################################################################
## #########################################################################
## #########################################################################


# index by [race_track.lex_style]
lex_func = {
    'Arduino2014' : lex_Arduino2014 ,
    'Fantasy2014' : lex_Fantasy2014
    }


## these are courtesy functions used by FakeCar
## we put them here because the decoders / lexers are right up there a few lines
def enc_Arduino2014( rt,s,c,t ) :
    b = (1<<c) + ((1<<c)-1) # bit code
    e = Physical.convert( ( t, rt.tick_rate ), 'sec','tick,rate' )
    return "{}{}{:08X}".format(s,b,e)
def enc_Fantasy2014( rt,s,c,t ) :
    b = (s<<1) | c # bit code
    e = Physical.convert( ( t, rt.tick_rate ), 'sec','tick,rate' )
    return "{:02X}{:08X}".format(b,e)
# index by [race_track.lex_style]
enc_func = {
    'Arduino2014' : enc_Arduino2014 ,
    'Fantasy2014' : enc_Fantasy2014
    }
def encode_packet(rt,s,c,t) :
    return enc_func[rt.lex_style](rt,s,c,t)






## ##########################################################################
## ##########################################################################
## ##########################################################################

## ##########################################################################
## ##########################################################################
## ##########################################################################

MAX_THREAD_DELAY = 0.1 # seconds
    # ANYTHING that waits, should never wait longer than this amount

receiver_thread = None   # of class ProcessPacket
event_list = None
event_list_names = [
    'idle',
    'run',
    'stop',
    'exit',
    'gone'
    ]

race_track = None
pass_data = None
fn_update = None

receiver_verbose = False
def rcv_print(*msg) :
    global receiver_verbose
    if receiver_verbose :
        #print( 'Receiver:',*msg, flush=True )
        # uh ya, python3.2 doesn't have flush
        # bad python, bad
        print( 'Receiver:', *msg )


def Create(verbose=False) :
    global receiver_thread, event_list, event_list_names, receiver_verbose

    receiver_verbose = verbose

    rcv_print("Create -- enter")

    # there can be only one
    assert( receiver_thread is None )

    # build the events -- all as unsignaled
    # don't build twice
    if event_list is None :
        event_list = {}
        for k in event_list_names :
            event_list[k] = threading.Event()

    # create the thread
    receiver_thread = threading.Thread(target=packet_process_func)
    assert( receiver_thread is not None )

    # start the thread
    receiver_thread.start()

    rcv_print("Create -- leave")


def Start(pd,rt,fn) :
    """ start recving real time data from the passed in pd
        always check the return value
        if false, then the recever was busy and needs to be stopped first
        otherwise, reciever is running
    """
    global event_list
    global race_track, pass_data, fn_update

    rcv_print( "Start -- enter" )

    # make sure we're still alive
    if event_list['gone'].is_set() :
        raise Exception(" ... can't start becuase the receiver thread crashed ... ")

    if event_list['run'].is_set() :
        rcv_print( "Start -- leaving -- already set" )
        return False

    pass_data  = pd
    race_track = rt
    fn_update  = fn

    event_list['run'].set()

    rcv_print( "Start -- leave" )

    return True

def Stop(wait_for_idle) :

    global event_list

    # if we're not running, then we're already stopped
    rcv_print( "Stop -- enter" )

    if event_list['run'].is_set() :
        event_list['stop'].set()
        if wait_for_idle>0 :
            return WaitIdle(wait_for_idle)

    rcv_print( "Stop -- leave" )
    return True



def WaitIdle(timeout) :
    """ blocks the caller until idle becomes set
        returns True if idle was set
        returns False if timeout happend first
    """

    rcv_print("WaitIdle -- enter")

    end_t = now_t() + timeout
    # thread might not have acknowledged the run yet
    while event_list['run'].is_set() and now_t() < end_t :
        # and the thread may have crashed
        if event_list['exit'].wait(MAX_THREAD_DELAY) :
            # the thread crashed !!
            # so do we throw ??
            raise Exception("... um, the receiver thread appears to have crashed ... ")


    # and we might really be stuck
    if event_list['run'].is_set() :
        rcv_print("WaitIdle -- leave")
        return False

    # now wait on idle
    ret = event_list['idle'].wait( end_t - now_t() )
    rcv_print("WaitIdle -- leave")
    return ret



def Destroy() :

    global receiver_thread, event_list

    rcv_print("Destroy -- enter")
    # there must be one
    if receiver_thread is not None :
        # make sure it's still alive
        if receiver_thread.is_alive() :
            if not event_list['gone'].is_set() :
                Stop(0)
                event_list['exit'].set()
                while not event_list['gone'].wait(MAX_THREAD_DELAY) :
                    rcv_print("   waiting for 'gone'")
                    # print should never be hit
        receiver_thread = None # reset for later
    # clear all of the events for next time
    for ev in event_list.values() : ev.clear()

    rcv_print("Destroy -- leave")


## ##########################################################################
## ##########################################################################

## ##########################################################################
## ##########################################################################
## ##########################################################################

##  receivers
##
## recv_X must return None or a valid PacketEvent
## they are constructed as generators
## they run for as long as they have data
## or until told to stop


# receiver for FakeCar
def recv_FakeCar(res,pd) :
    """ recv for FakeCar data
        yields None or True
        res must be a list with at least one item
        res[0] is used as the return value
        res[0] will be either None or a PacketEvent
        the return value from the generater cann be ignored

        pd must be the PassData from a FakeCar

        delays upto MAX_THREAD_DELAY
        terminates when read data complete
        or when told to 'stop'
    """
    beg_t = now_t() ;
    for (nxt_t,pkt) in pd.packet_event_list.pel :
        while nxt_t+beg_t > now_t() :
            if event_list['stop'].wait( min( MAX_THREAD_DELAY, nxt_t+beg_t-now_t() ) ) :
                # if wait returned true, then stop event was signaled
                return
            # otherwise, yield None, still waiting
            yield None
        # now we are at the nxt pkt time
        res[0] = PacketEvent(now_t(),pkt)
        yield True
        res[0] = None

    # and that's it


def recv_Arduino2014(res,pd) :
    assert(False) # not written yet
#    open the com port
#    start text buffer
#    while characters to read
#        append to text buffer
#    when packet has enough chars, yield it
#    otherwise yeild none
#
#    until told to stop
#    close tha com port




def generic_scanner(res,pd) :
    """ generic scanner for any complete PassData
        yields None or True
        res must be a list with at least one item
        res[0] is used as the return value
        res[0] will be either None or a PacketEvent
        the return value from the generater cann be ignored

        pd must be a complete PassData

        operates as fast as it can
        then exits
    """
    for pe in pd.packet_event_list.pel :
        # we are at the nxt pkt time
        res[0] = pe
        yield True
        res[0] = None

    # and that's it







## ########################################################################
## ########################################################################
## ########################################################################

# dictionary of recv_funcs
# index by [race_track.recv_style]

recv_func = {
    'Arduino2014' : recv_Arduino2014 ,
    'FakeCar' : recv_FakeCar
    }




## ########################################################################
## ########################################################################
## ########################################################################





def packet_process_func() :

    global event_list
    global race_track,pass_data,fn_update

    try:

        rcv_print("packet_process_func -- enter")

        # outter loop -- terminates on 'exit'
        while not event_list['exit'].is_set() :

            # sleep if not doing anything
            if not event_list['run'].is_set() :
                event_list['idle'].set()
                if event_list['exit'].wait(MAX_THREAD_DELAY) :
                    break
                continue

            # run is set -- so race_trace, pass_data, fn_update are all good to go
            # inner loop -- terminates when pkt recv exits
            event_list['idle'].clear()

            #emit the beg event
            fn_update( 'beg',(pass_data,race_track) )

            # set up our loop using the # concurrent iterators
            stl = race_track.new_stl()
            pel = []
            res = [None,None] # concurrent iterators [ PacketEvent, (s,c,nc) ]
            # determine the recv and lexer function
            beg_t = pass_data.timestamp
            if beg_t is None : # use one of the timed receivers
                recv = recv_func[race_track.recv_style]
                beg_t = now_t()
            else : recv = generic_scanner # otherwise, generic reciver
            lexer = lex_func[race_track.lex_style]
            # now do the concurrent iterators
            for _ in zip( recv(res,pass_data), lexer(res) ) :
                if res[0] :
                    pel.append( res[0] )
                if res[1] :
                    (s,c,nc) = res[1] # unpack
                    t_nom = Physical.convert( (nc,race_track.tick_rate),'tick,rate' )
                    stl[s][c] = Physical.Measurement(t_nom,race_track.time_accuracy)
                    fn_update( 'run',(pass_data,race_track,stl,s,c) )

            # recv is done or 'stop' was encountered
            # define a new pass data
            new_pd = pass_data._replace(
                timestamp = beg_t,
                packet_event_list = PacketEventList(pel),
                track_name = race_track.name )
            # emit the end event
            fn_update( 'end', ( new_pd,race_track,stl) )
            # reset for next go'round
            race_track = None
            pass_data = None
            fn_update = None
            # do these need to be interlocked ?? .. doesn't seem likethay do
            event_list['stop'].clear()  # always clear stop first !!
            event_list['run'].clear() # always clear run last !!
            # back to main while loop

        ## the while has exited
        rcv_print("packet_process_func -- leave ???!!!")

    finally:

        rcv_print("packet_process_func -- leave")
        event_list['idle'].set() # just in case somebody cares
        event_list['stop'].clear()  # always clear stop first !!
        event_list['run'].clear() # always clear run last !!
        event_list['exit'].set()
        event_list['gone'].set() # set this outer knows that we're gone
